MapMetadataResolver.java
package org.codefilarete.stalactite.engine.configurer.dslresolver;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.codefilarete.reflection.AccessorByMethodReference;
import org.codefilarete.reflection.AccessorDefinition;
import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
import org.codefilarete.reflection.ValueAccessPoint;
import org.codefilarete.reflection.ValueAccessPointMap;
import org.codefilarete.stalactite.dsl.embeddable.EmbeddableMappingConfiguration;
import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
import org.codefilarete.stalactite.dsl.entity.EntityMappingConfigurationProvider;
import org.codefilarete.stalactite.dsl.naming.ColumnNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.JoinColumnNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.MapTableNamingStrategy;
import org.codefilarete.stalactite.engine.configurer.NamingConfiguration;
import org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMapping;
import org.codefilarete.stalactite.engine.configurer.dslresolver.InheritanceConfigurationResolver.ResolvedConfiguration;
import org.codefilarete.stalactite.engine.configurer.dslresolver.MetadataSolvingCache.EntitySource;
import org.codefilarete.stalactite.engine.configurer.map.KeyValueRecord;
import org.codefilarete.stalactite.engine.configurer.map.MapRelation;
import org.codefilarete.stalactite.engine.configurer.model.DirectRelationJoin;
import org.codefilarete.stalactite.engine.configurer.model.Entity;
import org.codefilarete.stalactite.engine.configurer.model.IdentifierMapping;
import org.codefilarete.stalactite.engine.configurer.model.ResolvedMapRelation;
import org.codefilarete.stalactite.engine.configurer.model.ResolvedMapRelation.CompositeMemberMapping;
import org.codefilarete.stalactite.engine.configurer.model.ResolvedMapRelation.EntryMemberMapping;
import org.codefilarete.stalactite.engine.configurer.model.ResolvedMapRelation.MapMemberAsEntity;
import org.codefilarete.stalactite.engine.configurer.model.ResolvedMapRelation.ScalarMemberMapping;
import org.codefilarete.stalactite.query.api.JoinLink;
import org.codefilarete.stalactite.sql.ConnectionConfiguration;
import org.codefilarete.stalactite.sql.Dialect;
import org.codefilarete.stalactite.sql.ddl.Size;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.ForeignKey;
import org.codefilarete.stalactite.sql.ddl.structure.Key;
import org.codefilarete.stalactite.sql.ddl.structure.Key.KeyBuilder;
import org.codefilarete.stalactite.sql.ddl.structure.Key.KeySupport;
import org.codefilarete.stalactite.sql.ddl.structure.KeyMapping;
import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.result.BeanRelationFixer;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.collection.KeepOrderMap;
import org.codefilarete.tool.collection.KeepOrderSet;
import org.codefilarete.tool.collection.Maps;
import static org.codefilarete.reflection.DefaultReadWritePropertyAccessPoint.fromMethodReference;
import static org.codefilarete.tool.Nullable.nullable;
import static org.codefilarete.tool.bean.Objects.preventNull;
import static org.codefilarete.tool.collection.Iterables.first;
/**
* Resolves Map DSL configuration to a {@link ResolvedMapRelation}.
* Supports scalar keys/values and entity keys/values (single or composite identifiers).
*/
public class MapMetadataResolver {
private static final AccessorDefinition RECORD_ID_ACCESSOR_DEFINITION = AccessorDefinition.giveDefinition(
new AccessorByMethodReference<>(KeyValueRecord<Object, Object, Object>::getId));
private static final AccessorDefinition MAP_ENTRY_KEY_ACCESSOR_DEFINITION = AccessorDefinition.giveDefinition(
new AccessorByMethodReference<Map.Entry<Object, Object>, Object>(Map.Entry::getKey));
private static final AccessorDefinition MAP_ENTRY_VALUE_ACCESSOR_DEFINITION = AccessorDefinition.giveDefinition(
new AccessorByMethodReference<Map.Entry<Object, Object>, Object>(Map.Entry::getValue));
private static final ReadWritePropertyAccessPoint<KeyValueRecord<Object, Object, Object>, Object> RECORD_KEY_ACCESSOR =
fromMethodReference(KeyValueRecord::getKey, KeyValueRecord::setKey);
private static final ReadWritePropertyAccessPoint<KeyValueRecord<Object, Object, Object>, Object> RECORD_VALUE_ACCESSOR =
fromMethodReference(KeyValueRecord::getValue, KeyValueRecord::setValue);
private final Dialect dialect;
private final ConnectionConfiguration connectionConfiguration;
public MapMetadataResolver(Dialect dialect, ConnectionConfiguration connectionConfiguration) {
this.dialect = dialect;
this.connectionConfiguration = connectionConfiguration;
}
<C, I> Set<EntitySource<?, ?>> resolve(EntitySource<C, I> source) {
KeepOrderSet<EntitySource<?, ?>> targetEntities = new KeepOrderSet<>();
source.getResolvedConfigurations().forEach(resolvedConfiguration ->
targetEntities.addAll(resolve(source.getEntity(), resolvedConfiguration)));
return targetEntities;
}
private <C, I> Set<EntitySource<?, ?>> resolve(Entity<C, I, ?> entity, ResolvedConfiguration<C, I> resolvedConfiguration) {
KeepOrderSet<EntitySource<?, ?>> targetEntities = new KeepOrderSet<>();
resolvedConfiguration.getMappingConfiguration().getMaps().forEach(mapRelation ->
targetEntities.addAll(resolve(entity, resolvedConfiguration, mapRelation)));
return targetEntities;
}
// X is either K or KID
// Y is either V or VID
private <X, Y, SRC, SRCID, K, KID, V, VID,
M extends Map<K, V>,
SRCTABLE extends Table<SRCTABLE>,
MAPTABLE extends Table<MAPTABLE>,
KTABLE extends Table<KTABLE>,
VTABLE extends Table<VTABLE>>
Set<EntitySource<?, ?>> resolve(Entity<SRC, SRCID, SRCTABLE> source,
ResolvedConfiguration<SRC, SRCID> resolvedConfiguration,
MapRelation<SRC, K, V, M> mapRelation) {
AccessorDefinition mapAccessorDefinition = AccessorDefinition.giveDefinition(mapRelation.getMapProvider());
PrimaryKey<SRCTABLE, SRCID> sourcePK = source.getTable().getPrimaryKey();
NamingConfiguration namingConfiguration = resolvedConfiguration.getNamingConfiguration();
MAPTABLE targetTable = determineMapTable(mapRelation, mapAccessorDefinition, namingConfiguration.getMapTableNamingStrategy());
Map<Column<SRCTABLE, ?>, Column<MAPTABLE, ?>> primaryKeyForeignKeyColumnMapping = buildPrimaryKeyForeignKeyColumnMapping(
mapRelation,
targetTable,
sourcePK,
namingConfiguration.getColumnNamingStrategy(),
namingConfiguration.getForeignKeyNamingStrategy());
Supplier<M> mapFactory = preventNull(
mapRelation.getMapFactory(),
Reflections.giveMapFactory((Class<M>) mapAccessorDefinition.getMemberType()));
BeanRelationFixer<SRC, KeyValueRecord<K, V, SRCID>> relationFixer = BeanRelationFixer.ofMapAdapter(
mapRelation.getMapProvider(),
mapRelation.getMapProvider(),
mapFactory,
(bean, input, map) -> map.put(input.getKey(), input.getValue()));
KeyBuilder<MAPTABLE, SRCID> targetKeyBuilder = Key.from(targetTable);
targetKeyBuilder.addAllColumns(primaryKeyForeignKeyColumnMapping.values());
DirectRelationJoin<SRCTABLE, MAPTABLE, SRCID> mapJoin = new DirectRelationJoin<>(sourcePK, targetKeyBuilder.build());
KeepOrderSet<EntitySource<?, ?>> targetEntities = new KeepOrderSet<>();
MapMemberAsEntity<K, KID, MAPTABLE, KTABLE, X> keyEntityDefinition = null;
EntryMemberMapping<X, MAPTABLE> keyMapping;
if (mapRelation.getKeyEntityConfigurationProvider() != null) {
EntitySource<K, KID> keyEntitySource = buildEntity((EntityMappingConfigurationProvider<K, KID>) mapRelation.getKeyEntityConfigurationProvider());
Entity<K, KID, KTABLE> keyEntity = keyEntitySource.getEntity();
targetEntities.add(keyEntitySource);
ForeignKey<MAPTABLE, KTABLE, KID> keyEntityReferenceMapping = buildForeignEntityColumnMapping(
keyEntitySource,
RECORD_KEY_ACCESSOR,
mapRelation.getKeyColumnName(),
mapRelation.getKeyColumnSize(),
targetTable,
namingConfiguration.getForeignKeyNamingStrategy(),
namingConfiguration.getJoinColumnNamingStrategy());
// building identifier mapping projected on Map Table
keyMapping = (EntryMemberMapping<X, MAPTABLE>) buildEntityMemberMapping(keyEntity.getIdentifierMapping(), keyEntityReferenceMapping);
// entry key columns must be part of the Map Table primary key
keyEntityReferenceMapping.getColumns().forEach(Column::primaryKey);
keyEntityDefinition = new MapMemberAsEntity<>(
keyEntity, keyEntityReferenceMapping, mapRelation.getKeyEntityRelationMode());
} else if (mapRelation.getKeyEmbeddableConfigurationProvider() != null) {
CompositeMemberMapping<K, MAPTABLE> compositeKeyMapping = buildCompositeMemberMapping(
targetTable,
namingConfiguration,
mapRelation.getKeyEmbeddableConfigurationProvider().getConfiguration(),
mapRelation.getOverriddenKeyColumnNames(),
mapRelation.getOverriddenKeyColumnSizes()
);
compositeKeyMapping.getMapping().values().forEach(Column::primaryKey);
keyMapping = (EntryMemberMapping<X, MAPTABLE>) compositeKeyMapping;
} else {
ScalarMemberMapping<K, MAPTABLE> scalarKeyMapping = buildScalarMemberMapping(targetTable,
namingConfiguration,
MAP_ENTRY_KEY_ACCESSOR_DEFINITION,
mapRelation.getKeyColumnName(),
mapRelation.getKeyType(),
mapRelation.getKeyColumnSize());
scalarKeyMapping.getColumn().primaryKey();
keyMapping = (EntryMemberMapping<X, MAPTABLE>) scalarKeyMapping;
}
MapMemberAsEntity<V, VID, MAPTABLE, VTABLE, Y> valueEntityDefinition = null;
EntryMemberMapping<Y, MAPTABLE> valueMapping;
if (mapRelation.getValueEntityConfigurationProvider() != null) {
EntitySource<V, VID> valueEntitySource = buildEntity((EntityMappingConfigurationProvider<V, VID>) mapRelation.getValueEntityConfigurationProvider());
Entity<V, VID, VTABLE> valueEntity = valueEntitySource.getEntity();
targetEntities.add(valueEntitySource);
ForeignKey<MAPTABLE, VTABLE, VID> valueEntityReferenceMapping = buildForeignEntityColumnMapping(
valueEntitySource,
RECORD_VALUE_ACCESSOR,
mapRelation.getValueColumnName(),
mapRelation.getValueColumnSize(),
targetTable,
namingConfiguration.getForeignKeyNamingStrategy(),
namingConfiguration.getJoinColumnNamingStrategy());
// building identifier mapping projected on Map Table
valueMapping = (EntryMemberMapping<Y, MAPTABLE>) buildEntityMemberMapping(valueEntity.getIdentifierMapping(), valueEntityReferenceMapping);
valueEntityDefinition = new MapMemberAsEntity<>(
valueEntity, valueEntityReferenceMapping, mapRelation.getValueEntityRelationMode());
} else if (mapRelation.getValueEmbeddableConfigurationProvider() != null) {
CompositeMemberMapping<V, MAPTABLE> compositeValueMapping = buildCompositeMemberMapping(
targetTable,
namingConfiguration,
mapRelation.getValueEmbeddableConfigurationProvider().getConfiguration(),
mapRelation.getOverriddenValueColumnNames(),
mapRelation.getOverriddenValueColumnSizes()
);
valueMapping = (EntryMemberMapping<Y, MAPTABLE>) compositeValueMapping;
} else {
valueMapping = (EntryMemberMapping<Y, MAPTABLE>) buildScalarMemberMapping(targetTable,
namingConfiguration,
MAP_ENTRY_VALUE_ACCESSOR_DEFINITION,
mapRelation.getValueColumnName(),
mapRelation.getValueType(),
mapRelation.getValueColumnSize());
}
ResolvedMapRelation<SRC, SRCID, K, KID, V, VID, M, SRCTABLE, MAPTABLE, KTABLE, VTABLE> relation = new ResolvedMapRelation<>(
mapRelation.getMapProvider(),
mapRelation.isFetchSeparately(),
mapJoin,
relationFixer,
mapFactory,
primaryKeyForeignKeyColumnMapping,
keyMapping,
valueMapping,
keyEntityDefinition,
valueEntityDefinition
);
source.addRelation(relation);
return targetEntities;
}
private <X, XID, XTABLE extends Table<XTABLE>, MAPTABLE extends Table<MAPTABLE>>
ForeignKey<MAPTABLE, XTABLE, XID> buildForeignEntityColumnMapping(EntitySource<X, XID> entitySource,
ReadWritePropertyAccessPoint<? extends KeyValueRecord<?, ?, ?>, ?> recordMemberAccessor,
@Nullable String columnName,
@Nullable Size columnSize,
MAPTABLE mapTable,
ForeignKeyNamingStrategy foreignKeyNamingStrategy,
JoinColumnNamingStrategy joinColumnNamingStrategy) {
PrimaryKey<XTABLE, XID> entityPrimaryKey = entitySource.<XTABLE>getEntity().getTable().getPrimaryKey();
KeepOrderMap<JoinLink<MAPTABLE, ?>, JoinLink<XTABLE, ?>> mapping = new KeepOrderMap<>();
if (!entityPrimaryKey.isComposed()) {
Column<XTABLE, XID> entityKeyColumn = (Column<XTABLE, XID>) first(entityPrimaryKey.getColumns());
String effectiveColumnName = nullable(columnName).getOr(() -> joinColumnNamingStrategy.giveName(recordMemberAccessor, entityKeyColumn));
Column<MAPTABLE, XID> maptableKeyColumn = mapTable.addColumn(
effectiveColumnName,
entityKeyColumn.getJavaType(),
nullable(columnSize).getOr(entityKeyColumn::getSize));
mapping.put(maptableKeyColumn, entityKeyColumn);
} else {
entityPrimaryKey.getColumns().forEach(entityKeyColumn -> {
Column<MAPTABLE, ?> maptableKeyColumn = mapTable.addColumn(entityKeyColumn.getName(), entityKeyColumn.getJavaType(), entityKeyColumn.getSize());
mapping.put(maptableKeyColumn, entityKeyColumn);
});
}
return mapTable.addForeignKey(foreignKeyNamingStrategy::giveName, new KeySupport<>(new KeepOrderSet<>(mapping.keySet())), new KeySupport<>(new KeepOrderSet<>(mapping.values())));
}
// X is K or V
private <X, XID, XTABLE extends Table<XTABLE>, MAPTABLE extends Table<MAPTABLE>>
EntryMemberMapping<XID, MAPTABLE> buildEntityMemberMapping(IdentifierMapping<X, XID> identifierMapping,
ForeignKey<MAPTABLE, XTABLE, XID> foreignKeyEntityReferenceMapping) {
if (identifierMapping instanceof CompositeIdentifierMapping) {
CompositeIdentifierMapping<X, XID, XTABLE> compositeIdentifierMapping = (CompositeIdentifierMapping<X, XID, XTABLE>) identifierMapping;
KeyMapping<XTABLE, MAPTABLE, ?> invertedMapping = new KeyMapping<>(foreignKeyEntityReferenceMapping.getReferencedKey(), foreignKeyEntityReferenceMapping);
Map<ReadWritePropertyAccessPoint<XID, ?>, Column<MAPTABLE, ?>> identifierMappingInMapTable = Maps.innerJoinOnValuesAndKeys(compositeIdentifierMapping.getCompositeKeyMapping().getMapping(), invertedMapping.getMapping());
return new CompositeMemberMapping<>(compositeIdentifierMapping.getCompositeKeyMapping().getBeanType(), identifierMappingInMapTable);
} else if (identifierMapping instanceof SingleIdentifierMapping) {
return new ScalarMemberMapping<>((Column<MAPTABLE, XID>) first(foreignKeyEntityReferenceMapping.getColumns()));
} else if (identifierMapping instanceof AssignedByAnotherIdentifierMapping) {
return buildEntityMemberMapping(((AssignedByAnotherIdentifierMapping<X, XID>) identifierMapping).getSource(), foreignKeyEntityReferenceMapping);
} else {
throw new IllegalArgumentException("Unknown identifier mapping type " + Reflections.toString(identifierMapping.getClass()));
}
}
private <X, XID> EntitySource<X, XID> buildEntity(EntityMappingConfigurationProvider<X, XID> mappingConfigurationProvider) {
EntityMappingConfiguration<X, XID> mappingConfiguration = mappingConfigurationProvider.getConfiguration();
InheritanceConfigurationResolver<X, XID> inheritanceConfigurationResolver = new InheritanceConfigurationResolver<>();
KeepOrderSet<ResolvedConfiguration<?, XID>> ancestorsConfigurations = inheritanceConfigurationResolver.resolveConfigurations(mappingConfiguration);
InheritanceMetadataResolver<X, XID, ?> inheritanceMetadataResolver = new InheritanceMetadataResolver<>(dialect, connectionConfiguration);
return inheritanceMetadataResolver.resolve(ancestorsConfigurations);
}
private <SRC, K, V, M extends Map<K, V>, MAPTABLE extends Table<MAPTABLE>>
MAPTABLE determineMapTable(MapRelation<SRC, K, V, M> mapRelation,
AccessorDefinition mapAccessorDefinition,
MapTableNamingStrategy mapTableNamingStrategy) {
MAPTABLE targetTable = (MAPTABLE) mapRelation.getTargetTable();
if (targetTable == null) {
String tableName = nullable(mapRelation.getTargetTableName()).getOr(
() -> mapTableNamingStrategy.giveTableName(mapAccessorDefinition, mapRelation.getKeyType(), mapRelation.getValueType()));
targetTable = (MAPTABLE) new Table<>(tableName.replace('.', '_'));
}
return targetTable;
}
private <SRC, SRCID, K, V, M extends Map<K, V>, SRCTABLE extends Table<SRCTABLE>, MAPTABLE extends Table<MAPTABLE>>
Map<Column<SRCTABLE, ?>, Column<MAPTABLE, ?>> buildPrimaryKeyForeignKeyColumnMapping(MapRelation<SRC, K, V, M> mapRelation,
MAPTABLE targetTable,
PrimaryKey<SRCTABLE, SRCID> sourcePK,
ColumnNamingStrategy columnNamingStrategy,
ForeignKeyNamingStrategy foreignKeyNamingStrategy) {
Map<Column<SRCTABLE, ?>, Column<MAPTABLE, ?>> primaryKeyForeignColumnMapping = new HashMap<>();
if (!sourcePK.isComposed() && mapRelation.getReverseColumn() != null) {
primaryKeyForeignColumnMapping.put(first(sourcePK.getColumns()), (Column) mapRelation.getReverseColumn());
} else {
sourcePK.getColumns().forEach(col -> {
String reverseColumnName = nullable(mapRelation.getReverseColumnName()).getOr(
() -> columnNamingStrategy.giveName(RECORD_ID_ACCESSOR_DEFINITION));
Column<MAPTABLE, ?> reverseCol = targetTable.addColumn(reverseColumnName, col.getJavaType(), col.getSize())
.primaryKey();
primaryKeyForeignColumnMapping.put(col, reverseCol);
});
}
KeyBuilder<MAPTABLE, SRCID> keyBuilder = Key.from(targetTable);
keyBuilder.addAllColumns(primaryKeyForeignColumnMapping.values());
Key<MAPTABLE, SRCID> reverseKey = keyBuilder.build();
targetTable.addForeignKey(foreignKeyNamingStrategy::giveName, reverseKey, sourcePK);
return primaryKeyForeignColumnMapping;
}
private <X, MAPTABLE extends Table<MAPTABLE>>
CompositeMemberMapping<X, MAPTABLE>
buildCompositeMemberMapping(MAPTABLE mapTable,
NamingConfiguration namingStrategy,
EmbeddableMappingConfiguration<X> embeddableMappingConfiguration,
ValueAccessPointMap<X, String, ValueAccessPoint<X>> overriddenKeyColumnNames,
ValueAccessPointMap<X, Size, ValueAccessPoint<X>> overriddenKeyColumnSizes) {
// a special configuration was given, we compute a EmbeddedClassMapping from it
PropertyMappingResolver<X, MAPTABLE> propertyMappingResolver = new PropertyMappingResolver<>(dialect.getColumnBinderRegistry());
EmbeddableMapping<X, MAPTABLE> entryKeyMapping = propertyMappingResolver.resolveAsMapping(embeddableMappingConfiguration, overriddenKeyColumnNames, overriddenKeyColumnSizes, new ValueAccessPointMap<>(), mapTable, namingStrategy.getColumnNamingStrategy());
return new CompositeMemberMapping<>(embeddableMappingConfiguration.getBeanType(), entryKeyMapping.getMapping());
}
private <X, MAPTABLE extends Table<MAPTABLE>>
ScalarMemberMapping<X, MAPTABLE>
buildScalarMemberMapping(MAPTABLE mapTable,
NamingConfiguration namingStrategy,
AccessorDefinition entryMemberAccessorDefinition,
@Nullable String columnName,
Class<X> scalarType,
@Nullable Size columnSize) {
String effectiveColumnName = nullable(columnName)
.getOr(() -> namingStrategy.getColumnNamingStrategy().giveName(entryMemberAccessorDefinition));
Column<MAPTABLE, X> maptableColumn = mapTable.addColumn(effectiveColumnName, scalarType, columnSize);
return new ScalarMemberMapping<>(maptableColumn);
}
}